﻿using System;
using System.Collections.Generic;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

using xEngine.UI;
using xEngine.Rendering;
using xEngine.Input;

namespace xEngine.Debugging
{
    public class XConsole
    {
        #region Singleton

        private static XConsole inst = new XConsole();
        private XConsole()
        {
            _history = new List<string>();
            _items = new List<Item>();
            _itemsProcessed = new List<Item>();
            MaxEntries = 100;
            _lastOffset = 5;
            _lineHeight = 1;
            _allowedKeys = LoadAllowedKeys();
            Command = string.Empty;
            LogWriter.OnWriteLine += new EventHandler<LogEventArgs>(OnWriteLine);
        }

        #endregion

        #region Member Variables

        private List<string> _history;
        private List<Item> _items;
        private List<Item> _itemsProcessed;
        private RenderTarget2D _rt;
        private int _lastOffset;
        private SpriteFont _font;
        private float _lineHeight;
        private float _scrollHeight;
        private float _scrollOffset;
        private int _height;

        private Dictionary<String, String> _allowedKeys;

        #endregion

        #region Properties

        public static XRenderer Owner { get; set; }
        public static int MaxEntries { get; set; }
        public static SpriteFont Font
        {
            get { return inst._font; }
            set
            {
                inst._font = value;
                inst._lineHeight = inst._font.MeasureString("Tj").Y;
                inst._scrollHeight = 15 * inst._lineHeight;
            }
        }
        public static string Command { get; private set; }

        private static int HistoryPointer { get; set; }

        #endregion

        #region Functions

        private Dictionary<String, String> LoadAllowedKeys()
        {
            Dictionary<String, String> d = new Dictionary<String, String>();

            d.Add("D1", "1");
            d.Add("D2", "2");
            d.Add("D3", "3");
            d.Add("D4", "4");
            d.Add("D5", "5");
            d.Add("D6", "6");
            d.Add("D7", "7");
            d.Add("D8", "8");
            d.Add("D9", "9");
            d.Add("D0", "0");
            d.Add("OemPlus", "=");
            d.Add("Q", "Q");
            d.Add("W", "W");
            d.Add("E", "E");
            d.Add("R", "R");
            d.Add("T", "T");
            d.Add("Y", "Y");
            d.Add("U", "U");
            d.Add("I", "I");
            d.Add("O", "O");
            d.Add("P", "P");
            d.Add("A", "A");
            d.Add("S", "S");
            d.Add("D", "D");
            d.Add("F", "F");
            d.Add("G", "G");
            d.Add("H", "H");
            d.Add("J", "J");
            d.Add("K", "K");
            d.Add("L", "L");
            d.Add("Z", "Z");
            d.Add("X", "X");
            d.Add("C", "C");
            d.Add("V", "V");
            d.Add("B", "B");
            d.Add("N", "N");
            d.Add("M", "M");
            d.Add("OemPeriod", ".");

            return d;
        }
        private void OnWriteLine(object s, LogEventArgs e)
        {
            Color c = Color.White;

            switch (e.LogLevel)
            {
                case LogLevel.Warning:
                    c = Color.Yellow;
                    break;

                case LogLevel.Error:
                    c = Color.Red;
                    break;
            }

            WriteLine(e.Message, c);
        }

        private static void ProcessLines()
        {
            if (Owner == null || Owner.GraphicsDevice == null || Owner.GraphicsDevice.IsDisposed || Font == null)
                return;

            int w = Owner.GraphicsDevice.Viewport.Width;
            int pos;
            
            String sub, rem;
            Vector2 m;

            foreach (Item item in inst._items)
            {
                rem = item.Text;

                do
                {
                    sub = rem;
                    rem = "";

                    m = Font.MeasureString((sub != "" ? sub : "Tj"));

                    while (w < m.X + 20)
                    {
                        pos = sub.LastIndexOf(' ');

                        if(pos == -1)
                            break;

                        rem = sub.Substring(pos + 1) + (rem != "" ? " " : "") + rem;
                        sub = sub.Substring(0, pos);

                        m = Font.MeasureString((sub != "" ? sub : "Tj"));
                    }

                    inst._itemsProcessed.Add(new Item(sub, inst._lastOffset, item.Color));

                    if (inst._itemsProcessed.Count > MaxEntries)
                        inst._itemsProcessed.RemoveAt(0);

                    inst._lastOffset += (int)m.Y;
                }
                while (rem != "");
            }

            inst._items.Clear();
        }
        private static void ScrollUp()
        {
            if (inst._lastOffset < inst._height - 20 - inst._lineHeight)
                return;

            inst._scrollOffset += inst._scrollHeight;
            if (inst._scrollOffset > inst._itemsProcessed.Count * inst._lineHeight - (inst._height - 20 - inst._lineHeight))
                inst._scrollOffset = inst._itemsProcessed.Count * inst._lineHeight - (inst._height - 20 - inst._lineHeight);
        }
        private static void ScrollDown()
        {
            inst._scrollOffset -= inst._scrollHeight;
            if (inst._scrollOffset < 0)
                inst._scrollOffset = 0;
        }
        private static void ProcessCommand()
        {
            if (Command == string.Empty)
                return;

            inst._scrollOffset = 0;

            string cmd = "", tmp = Command.ToLower();
            string[] param = null;

            if (Command.Contains(" "))
            {
                cmd = tmp.Substring(0, tmp.IndexOf(" "));
                tmp = tmp.Substring(tmp.IndexOf(" ") + 1);
                param = tmp.Split(' ');
            }

            if (!((param == null) ? Owner.ProcessConsoleCommand(tmp) : Owner.ProcessConsoleCommand(cmd, param)))
                WriteLine("Command `" + Command + "` not recognized!", Color.Red);

            inst._history.Insert(0, Command);
            HistoryPointer = -1;

            Command = string.Empty;
        }

        public static void Clear()
        {
            inst._itemsProcessed.Clear();
            inst._lastOffset = 5;
            inst._scrollOffset = 0;
        }
        public static void OnKeyPress(KeyEventArgs e)
        {
            e.ApplyLock = true;
            string key = e.Key.ToString();
            
            switch (e.Key)
            {
                case Keys.Back:
                    if (Command.Length > 0)
                    {
                        Command = Command.Remove(Command.Length - 1, 1);
                        //e.ApplyLock = false;
                    }
                    break;

                case Keys.Space:
                    Command += " ";
                    break;

                case Keys.PageDown:
                    ScrollDown();
                    break;

                case Keys.PageUp:
                    ScrollUp();
                    break;

                case Keys.Up:
                    HistoryPointer++;
                    if (HistoryPointer < inst._history.Count)
                        Command = inst._history[HistoryPointer];
                    else
                    {
                        HistoryPointer = -1;
                        Command = "";
                    }
                    break;

                case Keys.Down:
                    HistoryPointer--;
                    if (HistoryPointer > -1 && HistoryPointer < inst._history.Count)
                        Command = inst._history[HistoryPointer];
                    else
                    {
                        HistoryPointer = -1;
                        Command = "";
                    }
                    break;

                case Keys.Enter:
                    ProcessCommand();
                    break;

                default:
                    if(inst._allowedKeys.ContainsKey(key))
                        Command += inst._allowedKeys[key];
                    break;
            }
        }
        public static void VideoSettingsChanged()
        {
            if (Owner == null)
                return;

            inst._height = Owner.GraphicsDevice.Viewport.Height / 2;
            inst._scrollHeight = ((int)((inst._height - 20) / inst._lineHeight) - 1) * inst._lineHeight;
            inst._rt = Utils.Utils.CreateMainRenderTarget(false);

            ProcessLines();
        }
        public static void WriteLine(object text)
        {
            WriteLine(text, Color.White);
        }
        public static void WriteLine(object text, System.Drawing.Color color)
        {
            WriteLine(text, new Color(color.R, color.G, color.B, color.A));
        }
        public static void WriteLine(object text, Color color)
        {
            if (text == null)
            {
                text = "NULL";
                color = Color.Yellow;
            }

            string[] lines = text.ToString().Replace("\r", "").Split('\n');

            foreach (string line in lines)
                inst._items.Add(new Item(line, 0, color));

            ProcessLines();
        }
        public static void Render()
        {
            if (Owner == null)
                return;

            // Uses RT to avoid Antialiasing the font
            if(inst._rt == null)
                inst._rt = Utils.Utils.CreateMainRenderTarget(false);

            RenderManager.PushRenderTarget(inst._rt);
            Owner.SpriteBatch.Begin();
            Owner.SpriteBatch.Draw(GenericResources.BlankTexture, new Rectangle(0, 0, Owner.GraphicsDevice.Viewport.Width, inst._height), Color.Black * 0.9f);

            float offset = inst._lastOffset - inst._height + 20 + inst._lineHeight;
            foreach (Item item in inst._itemsProcessed)
            {
                Vector2 pos = new Vector2(5, item.Offset - offset + inst._scrollOffset);

                if (pos.Y >= 0 && pos.Y + inst._lineHeight <= inst._height - 15 - inst._lineHeight)
                    Owner.SpriteBatch.DrawString(Font, item.Text, pos, item.Color);
            }

            Owner.SpriteBatch.DrawString(Font, "> " + Command + "_", new Vector2(5, inst._height - 5 - inst._lineHeight), Color.White);

            Owner.SpriteBatch.End();
            RenderManager.PopRenderTarget();

            Owner.SpriteBatch.Begin();
            Owner.SpriteBatch.Draw(inst._rt, new Rectangle(0, 0, Owner.GraphicsDevice.Viewport.Width, Owner.GraphicsDevice.Viewport.Height), Color.White);
            Owner.SpriteBatch.End();
        }

        #endregion

        private class Item
        {
            public string Text { get; set; }
            public int Offset { get; set; }
            public Color Color { get; set; }

            public Item(string text, int offset, Color color)
            {
                Text = text;
                Offset = offset;
                Color = color;
            }
        }
    }
}
